#include "gd32f4_i2c.h"
//---------------------------------------------------速度控制----------------------
#if FAST
#define I2CS_DELAY() delay_1us(1) // 快速模式
#define I2C_SPEED 400000UL        // I2C速度400KHz
#else
#define I2CS_DELAY() delay_1us(5) // 标准模式
#define I2C_SPEED 100000UL        // I2C速度100KHz
#endif

#if (I2C_MODE_SW == 0) // 软实现
//---------------------------------------------------SCL线和SDA线控制------------------
// 写SCL线
static void W_SCL(int state) {
    gpio_bit_write(SCL_PORT, SCL_PIN, state ? SET : RESET);
    I2CS_DELAY();
}

// 写SDA线
static void W_SDA(int state) {
    gpio_bit_write(SDA_PORT, SDA_PIN, state ? SET : RESET);
    I2CS_DELAY();
}

// 读SDA线
static int R_SDA() {
    I2CS_DELAY();
    return gpio_input_bit_get(SDA_PORT, SDA_PIN);
}

static void GPIO_Config() {
    // 开漏 OD输出
    // SCL
    GPIO_Output_Cfg(SCL_RCU, SCL_PORT, SCL_PIN, GPIO_OTYPE_OD, 1);
    // SDA
    GPIO_Output_Cfg(SDA_RCU, SDA_PORT, SDA_PIN, GPIO_OTYPE_OD, 1);
}

// 起始信号----------------------------------------------------------------------------------
static void I2C_Start() {
    W_SDA(1); // 确保SDA高电平
    W_SCL(1); // 确保SCL高电平
    W_SDA(0); // 先拉低SDA线
    W_SCL(0); // 后拉低SCL线
}

// 终止信号----------------------------------------------------------------------------------
static void I2C_Stop() {
    W_SDA(0); // 拉低SDA线
    W_SCL(1); // 先拉高SCL线
    W_SDA(1); // 后拉高SDA线
}

// 发送1个字节式------------------------------------------------------------------------------
static void I2C_SendByte(uint8_t data) {
    for (int i = 0; i < 8; i++) {
        W_SDA(data & 0x80); // 发送最高位
        W_SCL(1);
        W_SCL(0);
        data <<= 1; // 左移一位
    }
}

// 接收1个字节-------------------------------------------------------------------------------
static int I2C_ReceiveByte() {
    uint8_t data = 0;
    W_SDA(1); // 刚进来时SCL为低，拉高SDA释放交给从机控制，主机进入读取状态

    // 读取数据
    for (uint8_t i = 0; i < 8; i++) {
        data <<= 1;  // 左移一位
        W_SCL(1);    // 拉高SCL线
        if (R_SDA()) // 读取SDA线状态
        {
            data |= 0x01; // 如果SDA为高电平，设置最低位
        }
        W_SCL(0); // 拉低SCL线，从机就会把下一个数据放在SDA线上
    }
    return data;
}

/**
 * @brief 主机发送应答信号
 * @param ack 0: 发送ACK, 1: 发送NACK
 */
static void I2C_SendAck(uint8_t ack) {
    // 一进来时SCL是低电平
    W_SDA(ack); // 发送应答位
    W_SCL(1);   // 拉高SCL线
    W_SCL(0);   // 拉低SCL线
}

// 主机接收应答--返回0代表应答，返回1代表无应答------------------------------------------------------------------------------
static uint8_t I2C_ReceiveAck() {
    uint8_t ack = 0; // 读取SDA线状态
    W_SDA(1);        // 刚进来时SCL为低，拉高SDA释放交给从机控制，主机进入读取状态

    W_SCL(1);      // 拉高SCL线
    ack = R_SDA(); // 读取SDA线状态
    W_SCL(0);      // 拉低SCL线
    return ack;    // 返回应答位
}
/**
 * @brief 通过I2C向指定设备的指定寄存器地址写入数据
 * @param dev_addr I2C设备地址(7位地址，不包含读写位)
 * @param reg_addr 设备内部寄存器地址
 * @param data 要写入的数据缓冲区指针
 * @param length 要写入的数据字节数
 * @return 操作状态码:
 */
i2c_ret_t MyI2C_Write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t length) {
    // 起始信号
    I2C_Start();
    // 发送 设备地址
    I2C_SendByte(dev_addr | 0); // 写操作地址
    // 接收应答,从机应答成功返回0
    if (I2C_ReceiveAck()) {
        I2C_Stop();
        return I2C_ERROR_DEV_ADDR; // 退出函数
    }

    // 发送 寄存器地址
    I2C_SendByte(reg_addr);

    // 等待应答
    if (I2C_ReceiveAck()) {
        I2C_Stop();
        return I2C_ERROR_REG_ADDR; // 退出函数
    }

    // 循环发送length个数据
    for (uint32_t i = 0; i < length; i++) {
        I2C_SendByte(data[i]); // 发送数据
        // 等待应答
        if (I2C_ReceiveAck()) {
            I2C_Stop();
            return I2C_ERROR_DATA_SEND; // 数据发送失败
        }
    }

    // 停止信号
    I2C_Stop();
    return I2C_SUCCESS; // 成功
}

/**
 * @brief 通过I2C从指定设备的指定寄存器地址读取数据
 * @param dev_addr I2C设备地址(7位地址，不包含读写位)
 * @param reg_addr 设备内部寄存器地址
 * @param data 用于存储读取数据的缓冲区指针
 * @param length 要读取的数据字节数
 * @return 操作状态码:
 */
i2c_ret_t MyI2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint32_t length) {
    // 起始信号
    I2C_Start();
    // 发送 设备地址
    I2C_SendByte(dev_addr | 0); // 写操作地址
    // 接收应答,从机应答成功返回0
    if (I2C_ReceiveAck()) {
        I2C_Stop();
        return I2C_ERROR_DEV_ADDR; // 设备地址发送失败
    }

    // 发送 寄存器地址
    I2C_SendByte(reg_addr);

    // 等待应答
    if (I2C_ReceiveAck()) {
        I2C_Stop();
        return I2C_ERROR_REG_ADDR; // 寄存器地址发送失败
    }

    // 重新起始信号
    I2C_Start();
    // 发送 设备地址
    I2C_SendByte(dev_addr | 1); // 读操作地址
    if (I2C_ReceiveAck()) {
        I2C_Stop();
        return I2C_ERROR_READ_ADDR; // 读地址发送失败
    }

    // 循环接收number个数据
    for (uint32_t i = 0; i < length; i++) {
        // 读数据
        data[i] = I2C_ReceiveByte();
        if (i != length - 1) {
            // 非最后一条发送ACK
            I2C_SendAck(0);
        } else {
            // 最后一条发送NACK
            I2C_SendAck(1);
        }
    }
    I2C_Stop();         // 停止信号
    return I2C_SUCCESS; // 成功
}

/**
 * @brief I2C设备扫描(返回的是8位地址)
 * @param device_addr 存储扫描到的设备地址,第0位存储扫描到的设备数量
 * @return 操作状态码
 */
i2c_ret_t MyI2C_Device_Scan(uint8_t *device_addr) {
    uint8_t count = 0; // 用于记录找到的设备数量
    // 扫描所有可能，7位地址的范围是从0000000到1111111二进制，即0x00到0x7F，内部addr << 1变成8位地址
    for (uint8_t addr = 0x01; addr < 0x80; addr++) {
        // 发送起始信号
        I2C_Start();

        // 发送设备地址(写模式)
        I2C_SendByte(addr << 1 | 0);

        // 检查是否收到ACK应答
        if (!I2C_ReceiveAck()) {
            // 在此地址找到设备，保存地址(8位，想返回7位给<<1去掉就行了)
            device_addr[count + 1] = addr << 1; // +1是因为索引0将存储设备总数
            count++;
        }
        // 停止I2C通信
        I2C_Stop();
    }
    // 在索引0处存储设备总数
    device_addr[0] = count;
    return I2C_SUCCESS; // 返回成功
}

#elif (I2C_MODE_SW == 1) // 硬实现

static void GPIO_Config() {
    GPIO_Output_Af_Cfg(SCL_RCU, SCL_PORT, SCL_PIN, SCL_PIN_AF, GPIO_OTYPE_OD, GPIO_PUPD_NONE);
    GPIO_Output_Af_Cfg(SDA_RCU, SDA_PORT, SDA_PIN, SDA_PIN_AF, GPIO_OTYPE_OD, GPIO_PUPD_NONE);
}

static void I2C_Config() {
    /*初始化I2C时钟*/
    rcu_periph_clock_enable(I2C_RCU);

    /* reset I2C */
    i2c_deinit(I2C_NUM);

    /* configure I2C clock */
    i2c_clock_config(I2C_NUM, I2C_SPEED, I2C_DTCY_2);

    /* 配置 I2C 地址模式----主设备 */
    i2c_mode_addr_config(I2C_NUM, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00);

    /* enable I2C */
    i2c_enable(I2C_NUM);

    /* 配置ACK自动应答*/
    i2c_ack_config(I2C_NUM, I2C_ACK_ENABLE);
}

i2c_ret_t MyI2C_Write(uint8_t dev_addr, uint8_t mem_addr, uint8_t *data, uint16_t length) {
    uint16_t timeout = 5000;

    /* 等待I2C总线空闲 */
    while (i2c_flag_get(I2C_NUM, I2C_FLAG_I2CBSY) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_BUSY;

    /* 发送起始条件 */
    i2c_start_on_bus(I2C_NUM);

    /* 等待起始条件发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_SBSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 发送设备地址(写) */
    i2c_master_addressing(I2C_NUM, dev_addr, I2C_TRANSMITTER);

    /* 等待地址发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_ADDSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 清除地址发送标志 */
    i2c_flag_clear(I2C_NUM, I2C_FLAG_ADDSEND);

    /* 发送寄存器地址 */
    i2c_data_transmit(I2C_NUM, mem_addr);

    /* 等待数据发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_TBE) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 发送数据 */
    for (uint16_t i = 0; i < length; i++) {
        i2c_data_transmit(I2C_NUM, data[i]);

        /* 等待数据发送完成 */
        timeout = 5000;
        while (!i2c_flag_get(I2C_NUM, I2C_FLAG_TBE) && (--timeout))
            ;
        if (timeout == 0)
            return I2C_TIMEOUT;
    }

    /* 发送停止条件 */
    i2c_stop_on_bus(I2C_NUM);

    return I2C_SUCCESS;
}

i2c_ret_t MyI2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint32_t number) {
    uint16_t timeout = 5000;

    /* 等待I2C总线空闲 */
    while (i2c_flag_get(I2C_NUM, I2C_FLAG_I2CBSY) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_BUSY;

    /* 发送起始条件 */
    i2c_start_on_bus(I2C_NUM);

    /* 等待起始条件发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_SBSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 发送设备地址(写) */
    i2c_master_addressing(I2C_NUM, dev_addr, I2C_TRANSMITTER);

    /* 等待地址发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_ADDSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 清除地址发送标志 */
    i2c_flag_clear(I2C_NUM, I2C_FLAG_ADDSEND);

    /* 发送寄存器地址 */
    i2c_data_transmit(I2C_NUM, reg_addr);

    /* 等待数据发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_TBE) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 发送重复起始条件 */
    i2c_start_on_bus(I2C_NUM);

    /* 等待起始条件发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_SBSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 发送设备地址(读) */
    i2c_master_addressing(I2C_NUM, dev_addr, I2C_RECEIVER);

    /* 等待地址发送完成 */
    timeout = 5000;
    while (!i2c_flag_get(I2C_NUM, I2C_FLAG_ADDSEND) && (--timeout))
        ;
    if (timeout == 0)
        return I2C_TIMEOUT;

    /* 根据读取数据长度配置ACK */
    if (number == 1) {
        /* 单字节读取，禁用ACK */
        i2c_ack_config(I2C_NUM, I2C_ACK_DISABLE);
    }

    /* 清除地址发送标志 */
    i2c_flag_clear(I2C_NUM, I2C_FLAG_ADDSEND);

    /* 如果只读取一个字节，立即发送停止条件 */
    if (number == 1) {
        i2c_stop_on_bus(I2C_NUM);
    }

    /* 接收数据 */
    for (uint32_t i = 0; i < number; i++) {
        /* 等待接收缓冲区非空 */
        timeout = 5000;
        while (!i2c_flag_get(I2C_NUM, I2C_FLAG_RBNE) && (--timeout))
            ;
        if (timeout == 0)
            return I2C_TIMEOUT;

        /* 如果是倒数第二个字节，禁用ACK */
        if (i == number - 2) {
            i2c_ack_config(I2C_NUM, I2C_ACK_DISABLE);
        }

        /* 如果是最后一个字节，发送停止条件 */
        if (i == number - 1) {
            i2c_stop_on_bus(I2C_NUM);
        }

        /* 读取数据 */
        data[i] = i2c_data_receive(I2C_NUM);
    }

    /* 重新启用ACK */
    i2c_ack_config(I2C_NUM, I2C_ACK_ENABLE);

    return I2C_SUCCESS;
}

/**
 * @brief  扫描I2C总线上的已连接设备
 * @param  device_addr: 存储找到的设备地址的数组，第0位存放设备总数
 * @return I2C_SUCCESS: 操作成功
 */
uint8_t MyI2C_Device_Scan(uint8_t *device_addr) {
    uint8_t address;
    uint8_t found = 0;
    uint16_t timeout;
    // 请求总线空闲
    while (i2c_flag_get(I2C_NUM, I2C_FLAG_I2CBSY) && (--timeout))
        ;
    // 初始化设备计数
    device_addr[0] = 0;

    // 扫描所有可能的7位地址 (0x01 到 0x7F=128)
    for (address = 1; address < 128; address++) {
        timeout = 5000;

        // 等待I2C总线空闲
        while (i2c_flag_get(I2C_NUM, I2C_FLAG_I2CBSY) && (--timeout))
            ;
        if (timeout == 0)
            continue; // 如果总线忙，跳过此地址

        // 发送起始条件
        i2c_start_on_bus(I2C_NUM);

        // 等待起始条件发送完成
        timeout = 5000;
        while (!i2c_flag_get(I2C_NUM, I2C_FLAG_SBSEND) && (--timeout))
            ;
        if (timeout == 0) {
            i2c_stop_on_bus(I2C_NUM);
            continue;
        }

        // 发送设备地址(写模式)
        i2c_master_addressing(I2C_NUM, address << 1, I2C_TRANSMITTER);

        // 等待地址被应答
        timeout = 5000;
        while (!i2c_flag_get(I2C_NUM, I2C_FLAG_ADDSEND) &&
               !i2c_flag_get(I2C_NUM, I2C_FLAG_AERR) &&
               (--timeout))
            ;

        // 检查地址是否被应答(设备存在)
        if (i2c_flag_get(I2C_NUM, I2C_FLAG_ADDSEND)) {
            // 找到设备
            i2c_flag_clear(I2C_NUM, I2C_FLAG_ADDSEND);
            found++;

            // 存储设备地址 - 索引0存储计数，从索引1开始存储地址
            device_addr[found] = address << 1; // 左移1位转换为写模式地址
        } else if (i2c_flag_get(I2C_NUM, I2C_FLAG_AERR)) {
            // 清除地址错误标志
            i2c_flag_clear(I2C_NUM, I2C_FLAG_AERR);
        }

        // 发送停止条件
        i2c_stop_on_bus(I2C_NUM);
    }

    // 存储找到的设备数量
    device_addr[0] = found;

    return I2C_SUCCESS;
}

#endif

void MyI2C_Init() {
#if (I2C_MODE_SW == 0)
    GPIO_Config(); // 软实现GPIO配置
#elif (I2C_MODE_SW == 1)
    GPIO_Config(); // 硬实现GPIO配置
    I2C_Config();  // 硬实现I2C配置
#endif
}
